summaryrefslogtreecommitdiffstats
path: root/src/video_core/texture_cache/image_base.cpp
blob: 91512022fef9788d60a24bce97289683f189a13a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <optional>
#include <utility>
#include <vector>

#include "common/common_types.h"
#include "common/div_ceil.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/formatter.h"
#include "video_core/texture_cache/image_base.h"
#include "video_core/texture_cache/image_view_info.h"
#include "video_core/texture_cache/util.h"

namespace VideoCommon {

using VideoCore::Surface::DefaultBlockHeight;
using VideoCore::Surface::DefaultBlockWidth;

namespace {
/// Returns the base layer and mip level offset
[[nodiscard]] std::pair<s32, s32> LayerMipOffset(s32 diff, u32 layer_stride) {
    if (layer_stride == 0) {
        return {0, diff};
    } else {
        return {diff / layer_stride, diff % layer_stride};
    }
}

[[nodiscard]] bool ValidateLayers(const SubresourceLayers& layers, const ImageInfo& info) {
    return layers.base_level < info.resources.levels &&
           layers.base_layer + layers.num_layers <= info.resources.layers;
}

[[nodiscard]] bool ValidateCopy(const ImageCopy& copy, const ImageInfo& dst, const ImageInfo& src) {
    const Extent3D src_size = MipSize(src.size, copy.src_subresource.base_level);
    const Extent3D dst_size = MipSize(dst.size, copy.dst_subresource.base_level);
    if (!ValidateLayers(copy.src_subresource, src)) {
        return false;
    }
    if (!ValidateLayers(copy.dst_subresource, dst)) {
        return false;
    }
    if (copy.src_offset.x + copy.extent.width > src_size.width ||
        copy.src_offset.y + copy.extent.height > src_size.height ||
        copy.src_offset.z + copy.extent.depth > src_size.depth) {
        return false;
    }
    if (copy.dst_offset.x + copy.extent.width > dst_size.width ||
        copy.dst_offset.y + copy.extent.height > dst_size.height ||
        copy.dst_offset.z + copy.extent.depth > dst_size.depth) {
        return false;
    }
    return true;
}
} // Anonymous namespace

ImageBase::ImageBase(const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_)
    : info{info_}, guest_size_bytes{CalculateGuestSizeInBytes(info)},
      unswizzled_size_bytes{CalculateUnswizzledSizeBytes(info)},
      converted_size_bytes{CalculateConvertedSizeBytes(info)}, scale_rating{}, scale_tick{},
      has_scaled{}, gpu_addr{gpu_addr_}, cpu_addr{cpu_addr_},
      cpu_addr_end{cpu_addr + guest_size_bytes}, mip_level_offsets{CalculateMipLevelOffsets(info)} {
    if (info.type == ImageType::e3D) {
        slice_offsets = CalculateSliceOffsets(info);
        slice_subresources = CalculateSliceSubresources(info);
    }
}

ImageBase::ImageBase(const NullImageParams&) {}

ImageMapView::ImageMapView(GPUVAddr gpu_addr_, VAddr cpu_addr_, size_t size_, ImageId image_id_)
    : gpu_addr{gpu_addr_}, cpu_addr{cpu_addr_}, size{size_}, image_id{image_id_} {}

std::optional<SubresourceBase> ImageBase::TryFindBase(GPUVAddr other_addr) const noexcept {
    if (other_addr < gpu_addr) {
        // Subresource address can't be lower than the base
        return std::nullopt;
    }
    const u32 diff = static_cast<u32>(other_addr - gpu_addr);
    if (diff > guest_size_bytes) {
        // This can happen when two CPU addresses are used for different GPU addresses
        return std::nullopt;
    }
    if (info.type != ImageType::e3D) {
        const auto [layer, mip_offset] = LayerMipOffset(diff, info.layer_stride);
        const auto end = mip_level_offsets.begin() + info.resources.levels;
        const auto it = std::find(mip_level_offsets.begin(), end, static_cast<u32>(mip_offset));
        if (layer > info.resources.layers || it == end) {
            return std::nullopt;
        }
        return SubresourceBase{
            .level = static_cast<s32>(std::distance(mip_level_offsets.begin(), it)),
            .layer = layer,
        };
    } else {
        // TODO: Consider using binary_search after a threshold
        const auto it = std::ranges::find(slice_offsets, diff);
        if (it == slice_offsets.cend()) {
            return std::nullopt;
        }
        return slice_subresources[std::distance(slice_offsets.begin(), it)];
    }
}

ImageViewId ImageBase::FindView(const ImageViewInfo& view_info) const noexcept {
    const auto it = std::ranges::find(image_view_infos, view_info);
    if (it == image_view_infos.end()) {
        return ImageViewId{};
    }
    return image_view_ids[std::distance(image_view_infos.begin(), it)];
}

void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id) {
    image_view_infos.push_back(view_info);
    image_view_ids.push_back(image_view_id);
}

bool ImageBase::IsSafeDownload() const noexcept {
    // Skip images that were not modified from the GPU
    if (False(flags & ImageFlagBits::GpuModified)) {
        return false;
    }
    // Skip images that .are. modified from the CPU
    // We don't want to write sensitive data from the guest
    if (True(flags & ImageFlagBits::CpuModified)) {
        return false;
    }
    if (info.num_samples > 1) {
        LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
        return false;
    }
    return true;
}

void ImageBase::CheckBadOverlapState() {
    if (False(flags & ImageFlagBits::BadOverlap)) {
        return;
    }
    if (!overlapping_images.empty()) {
        return;
    }
    flags &= ~ImageFlagBits::BadOverlap;
}

void ImageBase::CheckAliasState() {
    if (False(flags & ImageFlagBits::Alias)) {
        return;
    }
    if (!aliased_images.empty()) {
        return;
    }
    flags &= ~ImageFlagBits::Alias;
}

void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) {
    static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format;
    ASSERT(lhs.info.type == rhs.info.type);
    std::optional<SubresourceBase> base;
    if (lhs.info.type == ImageType::Linear) {
        base = SubresourceBase{.level = 0, .layer = 0};
    } else {
        // We are passing relaxed formats as an option, having broken views/bgr or not won't matter
        static constexpr bool broken_views = false;
        static constexpr bool native_bgr = true;
        base = FindSubresource(rhs.info, lhs, rhs.gpu_addr, OPTIONS, broken_views, native_bgr);
    }
    if (!base) {
        LOG_ERROR(HW_GPU, "Image alias should have been flipped");
        return;
    }
    const PixelFormat lhs_format = lhs.info.format;
    const PixelFormat rhs_format = rhs.info.format;
    const Extent2D lhs_block{
        .width = DefaultBlockWidth(lhs_format),
        .height = DefaultBlockHeight(lhs_format),
    };
    const Extent2D rhs_block{
        .width = DefaultBlockWidth(rhs_format),
        .height = DefaultBlockHeight(rhs_format),
    };
    const bool is_lhs_compressed = lhs_block.width > 1 || lhs_block.height > 1;
    const bool is_rhs_compressed = rhs_block.width > 1 || rhs_block.height > 1;
    const s32 lhs_mips = lhs.info.resources.levels;
    const s32 rhs_mips = rhs.info.resources.levels;
    const s32 num_mips = std::min(lhs_mips - base->level, rhs_mips);
    AliasedImage lhs_alias;
    AliasedImage rhs_alias;
    lhs_alias.id = rhs_id;
    rhs_alias.id = lhs_id;
    lhs_alias.copies.reserve(num_mips);
    rhs_alias.copies.reserve(num_mips);
    for (s32 mip_level = 0; mip_level < num_mips; ++mip_level) {
        Extent3D lhs_size = MipSize(lhs.info.size, base->level + mip_level);
        Extent3D rhs_size = MipSize(rhs.info.size, mip_level);
        if (is_lhs_compressed) {
            lhs_size.width = Common::DivCeil(lhs_size.width, lhs_block.width);
            lhs_size.height = Common::DivCeil(lhs_size.height, lhs_block.height);
        }
        if (is_rhs_compressed) {
            rhs_size.width = Common::DivCeil(rhs_size.width, rhs_block.width);
            rhs_size.height = Common::DivCeil(rhs_size.height, rhs_block.height);
        }
        const Extent3D copy_size{
            .width = std::min(lhs_size.width, rhs_size.width),
            .height = std::min(lhs_size.height, rhs_size.height),
            .depth = std::min(lhs_size.depth, rhs_size.depth),
        };
        if (copy_size.width == 0 || copy_size.height == 0) {
            LOG_WARNING(HW_GPU, "Copy size is smaller than block size. Mip cannot be aliased.");
            continue;
        }
        const bool is_lhs_3d = lhs.info.type == ImageType::e3D;
        const bool is_rhs_3d = rhs.info.type == ImageType::e3D;
        const Offset3D lhs_offset{0, 0, 0};
        const Offset3D rhs_offset{0, 0, is_rhs_3d ? base->layer : 0};
        const s32 lhs_layers = is_lhs_3d ? 1 : lhs.info.resources.layers - base->layer;
        const s32 rhs_layers = is_rhs_3d ? 1 : rhs.info.resources.layers;
        const s32 num_layers = std::min(lhs_layers, rhs_layers);
        const SubresourceLayers lhs_subresource{
            .base_level = mip_level,
            .base_layer = 0,
            .num_layers = num_layers,
        };
        const SubresourceLayers rhs_subresource{
            .base_level = base->level + mip_level,
            .base_layer = is_rhs_3d ? 0 : base->layer,
            .num_layers = num_layers,
        };
        [[maybe_unused]] const ImageCopy& to_lhs_copy = lhs_alias.copies.emplace_back(ImageCopy{
            .src_subresource = lhs_subresource,
            .dst_subresource = rhs_subresource,
            .src_offset = lhs_offset,
            .dst_offset = rhs_offset,
            .extent = copy_size,
        });
        [[maybe_unused]] const ImageCopy& to_rhs_copy = rhs_alias.copies.emplace_back(ImageCopy{
            .src_subresource = rhs_subresource,
            .dst_subresource = lhs_subresource,
            .src_offset = rhs_offset,
            .dst_offset = lhs_offset,
            .extent = copy_size,
        });
        ASSERT_MSG(ValidateCopy(to_lhs_copy, lhs.info, rhs.info), "Invalid RHS to LHS copy");
        ASSERT_MSG(ValidateCopy(to_rhs_copy, rhs.info, lhs.info), "Invalid LHS to RHS copy");
    }
    ASSERT(lhs_alias.copies.empty() == rhs_alias.copies.empty());
    if (lhs_alias.copies.empty()) {
        return;
    }
    lhs.aliased_images.push_back(std::move(lhs_alias));
    rhs.aliased_images.push_back(std::move(rhs_alias));
    lhs.flags &= ~ImageFlagBits::IsRescalable;
    rhs.flags &= ~ImageFlagBits::IsRescalable;
}

} // namespace VideoCommon